package graph

import (
	"time"
	"errors"
	"bytes"
	"encoding/json"
	"net/url"
	"io/ioutil"
	"log"
	"strings"
	"net/http"
	"sync"
	"path"
	"io"
	"html/template"
	"code.google.com/p/feedgraph/rest"
	"labix.org/v2/mgo/bson"
	"labix.org/v2/mgo"
)

var broadcastTimeout = time.Minute

var m *mgo.Session

func init () {

	session, err := mgo.Dial("localhost")

	if err != nil {
		panic(err)
	}
	
	m = session
}

const MailBoxCap = 20

type mailbox chan interface{}

type Graph struct {
	mailboxes map[string]mailbox
	subscribers map[string]map[string]bool
	sync.Mutex
}

type Object interface{}

type Node struct {
	g *Graph
	Path string
	Object
}

type nodeset map[string]bool

var templFuncs = template.FuncMap{
	"Edges": db.Edges,
	"Link": db.Link,
	"Unlink": db.Unlink,
	"Store": db.Store,
	"Retrieve": db.Retrieve,
	"Render": db.renderToString,
	"Attachment": db.AttachmentHTML,
}

func New() *Graph {

	return &Graph{make(map[string]mailbox), make(map[string]map[string]bool), sync.Mutex{}}
}

func relation(rel, sink string) bson.M {

	b := make(bson.M)

	b["_id"] = rel + "&" + sink
	b["rel"] = rel
	b["sink"] = sink

	return b
}

func anonymous(obj interface{}) bson.M {

	b := make(bson.M)

	b["obj"] = obj

	return b
}

func match(rel string) bson.M {

	b := make(bson.M)

	b["rel"] = rel

	return b
}

func set(sub interface{}) bson.M {

	b := make(bson.M)

	b["$set"] = sub

	return b
}

func object(n string, obj interface{}) bson.M {

	b := make(bson.M)
	b["_id"] = n
	b["obj"] = obj

	return b
}

func node(n string) bson.M {

	b := make(bson.M)

	b["_id"] = n

	return b
}

func clean(s string) string {

	return path.Clean("/" + s)
}

func (g *Graph) Node(n string, obj interface{}) Node {

	return Node{g, n, obj} 
}

func (n Node) Relations() map[string]interface{} {

	return n.g.Edges(n.Path, "relations")
}

func (n Node) Edges(rel string) map[string]interface{} {

	return n.g.Edges(n.Path, rel)
}

func (n Node) Sinks(rel string) []string {

	return n.g.Sinks(n.Path, rel)
}

func (n Node) Parent() Node {

	dir := path.Dir(n.Path)

	if dir == "/" || dir == "." {
		dir = ""
	}

	obj, _ := n.g.Retrieve(dir)

	return Node{n.g, dir, obj}
}

func (n Node) Name() string {

	name, ok1 := n.Object.(bson.M)["Name"].(string)
	title, ok2 := n.Object.(bson.M)["Title"].(string)

	if ok1 {
		return name
	}

	if ok2 {
		return title
	}

	return path.Base(n.Path)
}

func (n Node) JSON() (string, error) {

	obj, err := n.g.Retrieve(n.Path)

	if err != nil {
		return "null", err
	}

	data, err := json.MarshalIndent(obj, "\n", " ")

	return string(data), err
}

func (n Node) Attachment() template.HTML {

	text, err := n.g.Attachment(n.Path)

	if err != nil {
		return template.HTML("")
	}

	return template.HTML(text)
}

func (g *Graph) Unlink(src, rel, sink string) error {

	g.Lock()
	defer g.Unlock()

	return g.unlink(src, rel, sink)
}

func (g *Graph) Link(src, rel, sink string) error {

	g.Lock()
	defer g.Unlock()
	
	return g.link(src, rel, sink)
}

func (g *Graph) link(n, rel, sink string) (err error) {

	n = clean(n)
	rel = clean(rel)
	sink = clean(sink)

	if rel == "/subscribers" {

		m, ok := g.subscribers[n]

		if !ok {
			m = make(map[string]bool)
			g.subscribers[n] = m
		}

		m[sink] = true

		return
	}

	_, err = m.DB("edges").C(n).Upsert(relation(rel, sink), relation(rel, sink))
	
	if err != nil {

		log.Println(err)

		return
	}	

	return
}

func (g *Graph) unlink(n, rel, sink string) (error) {

	n = clean(n)
	rel = clean(rel)
	sink = clean(sink)

	if rel == "/subscribers" {

		m, ok := g.subscribers[n]

		if !ok {
			m = make(map[string]bool)
			g.subscribers[n] = m
		}

		delete(m, sink)

		return nil
	}


	err := m.DB("edges").C(n).Remove(relation(rel, sink))

	if err != nil {

		log.Println(err)
		return err
	}
	
	return nil
}

func (g *Graph) Sinks(n, rel string) (sinks []string) {

	n = clean(n)
	rel = clean(rel)

	iter := m.DB("edges").C(n).Find(match(rel)).Iter()
	
	res := relation("", "")

	for iter.Next(res) {

		sink := res["sink"].(string)
	
		obj, err := g.Retrieve(sink)

		if obj != nil && err == nil {
			sinks = append(sinks, sink)
		}
	}

	return
}

func (g *Graph) Relations(n string) map[string]interface{} {
	return g.Edges(n, "/relations")
}


func (g *Graph) Edges(n, rel string) (nodes map[string]interface{}) {

	n = clean(n)
	rel = clean(rel)
	
	nodes = make(map[string]interface{})
	
	// for special case of subscribers, we expect subscriber nodes to be empty
	if rel == "/subscribers" {

		for sink := range g.subscribers[n] {
		
			nodes[sink] = true
		}
		
		return
	}

	iter := m.DB("edges").C(n).Find(match(rel)).Iter()

	res := relation("", "")

	for iter.Next(res) {

		sink := res["sink"].(string)
		obj, err := g.Retrieve(sink)

		if obj != nil && err == nil {
			nodes[sink] = obj
		}
	}

	return
}

func (g *Graph) Delete(n string) error {

	n = clean(n)

	m.DB("data").C("objects").Remove(node(n))

	for rel := range g.Relations(n) {

		for dest := range g.Edges(n, rel) {

			g.Unlink(n, rel, dest)
		}
	}

	return nil
}

func (g *Graph) Store(n string, obj interface{}) error {

	n = clean(n)

	_, err := m.DB("data").C("objects").Upsert(node(n), object(n, obj))
	
	if err != nil {
		log.Println(err)
		return err
	}

	dir := path.Dir(n)

	g.Link(dir, "/members", n)

	return nil
}

// attach raw data (not a json object) to a node.  A node with 
// a new attachment retains all previous properties.
func (g *Graph) Attach(n string, r io.Reader) error {

	n = clean(n)

	f, err := m.DB("data").GridFS("fs").Create(n)

	if err != nil {
		log.Println(err)
		return err
	}

	defer f.Close()
	io.Copy(f, r)

	return nil
}

// access the raw data attached to a node
func (g *Graph) Open(n string) (r io.ReadCloser, err error) {

	n = clean(n)

	r, err = m.DB("data").GridFS("fs").Open(n)

	if err != nil {
		log.Println(err)
	}

	return
}

func (g *Graph) AttachmentHTML(n string) (template.HTML, error) {

	text, err := g.Attachment(n)

	if err != nil {
		return template.HTML(""), err
	}

	return template.HTML(text), nil
}

func (g *Graph) Attachment(n string) (data []byte, err error) {

	f, err := g.Open(n)

	if err != nil {
		return 
	}

	return ioutil.ReadAll(f)
}

func (g *Graph) Retrieve(n string) (interface{}, error) {

	n = clean(n)

	var res bson.M

	err := m.DB("data").C("objects").Find(node(n)).One(&res)

	if err != nil {
		return empty, err
	}

	return res["obj"], nil
}

func (g *Graph) renderToString(n, with string) (template.HTML, error) {

	var buf bytes.Buffer

	err := g.RenderWith(&buf, n, with)

	return template.HTML(buf.String()), err
}

func (g *Graph) RenderWith(w io.Writer, n, with string) error {

	if with == "" {
		// use RenderWith property

		obj, err := g.Retrieve(n)

		if err != nil {
			return err
		}

		prop, ok := obj.(bson.M)["RenderWith"].(string)

		if ok {
			with = prop
		} else {
			return errors.New("no RenderWith field")
		}
	}

	r, err := g.Open(with)

	if err != nil {
		log.Println("no attachment for specified template node")
		return err
	}

	tmpl, err := ioutil.ReadAll(r)

	if err != nil {
		log.Println(err)
		return err
	}

	return g.Render(w, n, string(tmpl))
}

func (g *Graph) Render(w io.Writer, n string, tmpl string) error {

	obj, err := g.Retrieve(n)

	if err != nil {
		log.Println(err)
		obj = make(bson.M)
	}

	node := g.Node(n, obj)

	thisFuncs := template.FuncMap{
		"node": func () Node {
			return node
		},
	}
	
	html, err := template.New("html").Funcs(templFuncs).Funcs(thisFuncs).Parse(tmpl)

	if err != nil {
		log.Println(err)
		return err
	}

	err = html.Execute(w, node.Object)

	if err != nil {
		log.Println(err)
		return err
	}

	return nil
}

func (g *Graph) send(from, n string, msg interface{}, timeout time.Duration) {

	n = clean(n)

	g.Lock()

	mb, ok := g.mailboxes[n]

	if !ok {
		mb = make(mailbox)
		g.mailboxes[n] = mb
	}

	g.Unlock()

	// asynchronous so this operation never blocks
		
	select {
	case mb <- msg:
//		log.Println("sent", n, msg)
	case <-time.After(timeout):
//		log.Println("timed out sending", n, msg)
		// automatically unsubscribe if no one is listening
		g.Unlink(from, "/subscriber", n)
	}
}

func (g *Graph) Inbox(n string) mailbox {

	n = clean(n)

	g.Lock()

	mb, ok := g.mailboxes[n]

	if !ok {
		mb = make(mailbox)
		g.mailboxes[n] = mb
	}

	g.Unlock()

	return mb
}

// consume all msgs that are ready
func (g *Graph) InboxAll(n string) []interface{} {

	msgs := make([]interface{}, 0)

	mb := g.Inbox(n)

	for {
		select {
		case msg := <-mb:
			msgs = append(msgs, msg)
		default:
			return msgs
		}
	}
}

// consume all msgs that are ready -- block until at lease one is ready
func (g *Graph) InboxWait(n string) []interface{} {

	msgs := make([]interface{}, 0)

	mb := g.Inbox(n)

	msgs = append(msgs, <-mb)

	for {
		select {
		case msg := <-mb:
			msgs = append(msgs, msg)
		default:
			return msgs
		}
	}
}


// This function is the single point where distribution across the network is needed.
// For now, it only works with one process.  In the future, it should use the client API
// to send thru the mux.
func (g *Graph) Publish(src string, msg interface{}) {

	for n, _ := range g.Edges(src, "/subscribers") {
		if n != src {
			go g.send(src, n, msg, broadcastTimeout)
		}
	}
}

var db *Graph = New()
var empty = make(map[string]interface{})

func GraphHandler(w http.ResponseWriter, req *http.Request) {

	optionsHandler(w, req)

	addr := req.Header.Get("X-Forwarded-For")

	log.Println(addr, req.Method, req.RequestURI)

	ext := path.Ext(req.URL.Path)

		
	if ext == ".mailbox" {
		mailboxHandler(w, req)
	} else if ext == ".html" && req.Method == "GET" {
		renderHandler(w, req)
	} else if ext != "" {
		attachmentHandler(w, req)
	} else if req.URL.RawQuery == "" {
		nodeHandler(w, req)
	} else {
		relationHandler(w, req)
	}
}

func relationHandler(w http.ResponseWriter, req *http.Request) {

	// node/path?relation&sink/node

	src := req.URL.Path
	parts := strings.SplitN(req.URL.RawQuery, "&", 2)
	rel, _ := url.QueryUnescape(parts[0])
	sink := ""

	if len(parts) == 2 {
		sink, _ = url.QueryUnescape(parts[1])
	}

	log.Println("relation request:", req.Method, src, rel, sink)
	
	if src == "" || rel == "" {
		log.Println("missing relation type")
//		w.WriteHeader(400)
		return
	}

	switch req.Method {
	case "GET":
		// GET list of nodes that satisfy the relation
		results := db.Edges(src, rel)
		rest.WriteObject(w, req, results)
	case "PUT":
		// link two nodes with relation
		if sink == "" {
			w.WriteHeader(400)
			log.Println("missing sink")
			return
		}
		db.Link(src, rel, sink)
	case "DELETE":
		if sink == "" {
			w.WriteHeader(400)
			log.Println("missing sink")
			return
		}
		db.Unlink(src, rel, sink)

	}

}

func renderHandler(w http.ResponseWriter, req *http.Request) {

	n := req.URL.Path
	ext := path.Ext(n)
	n = strings.TrimSuffix(n, ext)
	t := req.FormValue("t")

	log.Println("render request", req.Method, n, t)
	w.Header().Set("Content-Type", "text/html")

	switch req.Method {

	case "GET":
	
		err := db.RenderWith(w, n, t)

		if err != nil {
			w.WriteHeader(500)
			log.Println(err)
			return
		}
	}
}

func attachmentHandler(w http.ResponseWriter, req *http.Request) {

	n := req.URL.Path
	ext := path.Ext(n)
	n = strings.TrimSuffix(n, ext)
	t := req.FormValue("t")

	log.Println("attachment request", req.Method, n, t)

	switch ext {
	case ".css":
		w.Header().Set("Content-Type", "text/css")
	case ".js":
		w.Header().Set("Content-Type", "text/javascript")
	case ".html":
		w.Header().Set("Content-Type", "text/html")
	case ".txt":
		w.Header().Set("Content-Type", "text/plain")
	default:
		w.WriteHeader(400)
		log.Println("unsupported extension", ext)
		return
	}

	switch req.Method {

	case "GET":

		r, err := db.Open(n)

		if err != nil {
			w.WriteHeader(404)
			return
		}

		defer r.Close()
		io.Copy(w, r)

	case "PUT":

		err := db.Attach(n, req.Body)

		if err != nil {
			w.WriteHeader(400)
			return
		}
	}
}

func nodeHandler(w http.ResponseWriter, req *http.Request) {

	n := req.URL.Path
	ext := path.Ext(n)
	n = strings.TrimSuffix(n, ext)
	
	log.Println("node request", req.Method, n)

	if n == "" || req.URL.RawQuery != "" {
		log.Println("400 no node specified")
//		w.WriteHeader(400)
		return
	}

	switch req.Method {
	case "GET":
		// GET a node: return object stored there, or {}
		obj, err := db.Retrieve(n)

		if err != nil {
			log.Println(err)
			log.Println("404 not found")
			w.WriteHeader(404)
			return
		}
		
		rest.WriteObject(w, req, obj)
	case "PUT":
		// PUT a node: store object
		var obj interface{}
		err := rest.ReadObject(req, &obj)

		if err != nil {
			log.Println(err)
			log.Println("500 could not read object")
			w.WriteHeader(500)
			return
		}
	
		err = db.Store(n, obj)

		if err != nil {
			log.Println(err)
			log.Println("500 could not store object")
			w.WriteHeader(500)
			return
		}
	
	case "POST":
		// broadcast a message
		var obj interface{}
		err := rest.ReadObject(req, &obj)

		if err != nil {
			log.Println(err)
			log.Println("500 could not read object")
			w.WriteHeader(500)
			return
		}

		db.Publish(n, obj)
	case "DELETE":

		err := db.Delete(n)

		if err != nil {
			log.Println(err)
			log.Println("500 could not delete node")
			w.WriteHeader(500)
			return
		}

	default:
		log.Println("400 invalid request method")
//		w.WriteHeader(400)
	}
}

func mailboxHandler(w http.ResponseWriter, req *http.Request) {

	w.Header().Set("Cache-Control", "no-cache")

	n := req.URL.Path
	ext := path.Ext(n)
	n = strings.TrimSuffix(n, ext)
	
	log.Println("mailbox request", req.Method, n)

	msgs := db.InboxWait(n)

	rest.WriteObject(w, req, msgs)
}

func optionsHandler(w http.ResponseWriter, req *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Methods", "GET")
	w.Header().Add("Access-Control-Allow-Methods", "PUT")
	w.Header().Add("Access-Control-Allow-Methods", "POST")
	w.Header().Add("Access-Control-Allow-Methods", "DELETE")
	w.Header().Add("Access-Control-Allow-Methods", "HEAD")
	w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
}


                
